package com.zang.blogz.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zang.blogz.constant.ResultConstant;
import com.zang.blogz.constant.ScheduleConstant;
import com.zang.blogz.entity.Job;

import com.zang.blogz.mapper.JobMapper;
import com.zang.blogz.model.input.ro.JobRO;
import com.zang.blogz.model.input.ro.JobRunRO;
import com.zang.blogz.model.input.ro.JobSearchRO;
import com.zang.blogz.model.input.ro.JobStatusRO;
import com.zang.blogz.model.output.JobVO;

import com.zang.blogz.result.Result;
import com.zang.blogz.service.JobService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.github.vampireachao.stream.core.optional.Opp;

import com.zang.blogz.utils.CronUtils;
import com.zang.blogz.utils.ScheduleUtils;

import com.zang.blogz.utils.SearchQueryInit;
import io.github.vampireachao.stream.core.stream.Steam;
import lombok.SneakyThrows;
import org.quartz.JobKey;
import org.quartz.Scheduler;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

/**
 * <p>
 * 定时任务调度表 服务实现类
 * </p>
 *
 * @author 朵橙i
 * @since 2022-09-04
 */
@Service
public class JobServiceImpl extends ServiceImpl<JobMapper, Job> implements JobService {

    @Autowired
    private Scheduler scheduler;



    @SneakyThrows
    @PostConstruct
    public void init() {
        scheduler.clear();
        List<Job> jobs = list();
        for (Job job : jobs) {
            ScheduleUtils.createScheduleJob(scheduler, job);
        }
    }


    @SneakyThrows
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Result<JobRO> saveJob(JobRO jobRO) {

        checkCronIsValid(jobRO);

        boolean save = save(jobRO);

        if (save){

            ScheduleUtils.createScheduleJob(scheduler, jobRO);
        }

        return Result.ok(ResultConstant.SAVE_SUCCESS,jobRO);
    }

    @SneakyThrows
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Result<JobRO> updateJob(JobRO jobRO) {

        checkCronIsValid(jobRO);

        Job tempJob = getById(jobRO.getId());

        boolean b = updateById(jobRO);

        if (b){

            updateSchedulerJob(jobRO, tempJob.getJobGroup());
        }
        return Result.ok(ResultConstant.UPDATE_SUCCESS, jobRO);

    }

    @SneakyThrows
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Boolean> deleteByIds(List<Integer> jobIds) {

        LambdaQueryWrapper<Job> queryWrapper = new LambdaQueryWrapper<>();

        queryWrapper.in(Job::getId, jobIds);

        List<Job> jobList = list(queryWrapper);

        boolean isRemove = remove(queryWrapper);

        if (isRemove){

                Steam.of(jobList).forEach(job ->{

                    Opp<Object> ofTry = Opp.ofTry(() -> {

                        scheduler.deleteJob(ScheduleUtils.getJobKey(job.getId(), job.getJobGroup()));
                        return null;
                    });

                    if(ofTry.getException() != null){

                        throw new RuntimeException(ofTry.getException());
                    }
                } );

        }

        return Result.ok("批量删除成功",isRemove);
    }

    @Override
    public IPage<JobVO> listJobs(JobSearchRO jobSearchRO, Page<Job> page) {

        QueryWrapper<Job> queryWrapper = SearchQueryInit.getQueryWrapper(jobSearchRO, Job.class);

        Page<Job> jobPage = page(page, queryWrapper);

        return jobPage.convert(result -> {

            JobVO vo = JobVO.builder().build();
            BeanUtil.copyProperties(result, vo);
            return vo;
        });
    }

    @SneakyThrows
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<JobStatusRO> updateJobStatus(JobStatusRO jobStatusRO) {

        LambdaQueryWrapper<Job> queryWrapper = new LambdaQueryWrapper<>();

        Opp<Integer> of = Opp.of(jobStatusRO.getId());

        queryWrapper.eq(of.isPresent(), Job::getId, of.get());

        Job job = getOne(queryWrapper);

        Opp.of(job).orElseThrow(()->new NullPointerException("当前任务不存在"));

        if (job.getStatus().equals(jobStatusRO.getStatus())) {
            return null;
        }

        Integer status = jobStatusRO.getStatus();
        Integer jobId = job.getId();
        String jobGroup = job.getJobGroup();

        LambdaUpdateWrapper<Job> updateWrapper = new LambdaUpdateWrapper<>();

        updateWrapper.set(Job::getStatus, jobStatusRO.getStatus());
        updateWrapper.eq(Job::getId, jobStatusRO.getId());

        boolean update = update(updateWrapper);

        if (update){

            if (ScheduleConstant.Status.NORMAL.getValue().equals(status)) {

                scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup));
            } else if (ScheduleConstant.Status.PAUSE.getValue().equals(status)) {

                scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
            }
        }

        return Result.ok(ResultConstant.STATE_SUCCESS, jobStatusRO);

    }

    @Override
    public Result<Boolean> runJob(JobRunRO jobRunRO) {

        Integer jobId = jobRunRO.getId();
        String jobGroup = jobRunRO.getJobGroup();

        Opp<Object> objectOpp = Opp.ofTry(() -> {

            scheduler.triggerJob(ScheduleUtils.getJobKey(jobId, jobGroup));
            return null;
        });

        if (objectOpp.getException() != null){

            throw new RuntimeException(ResultConstant.JobMessage.JOB_ERROR);
        }

        return Result.ok(ResultConstant.JobMessage.JOB_SUCCESS, Boolean.TRUE);



    }

    @Override
    public List<String> listJobGroup() {

       return Steam.of(list()).distinct(Job::getJobGroup).map(Job::getJobGroup).toList();


    }

    @Override
    public Result<Boolean> deleteById(Integer id) {

        AtomicReference<Boolean> isD = new AtomicReference<>(Boolean.FALSE);
        Opp.of(getById(id)).ifPresent((j) -> {
            isD.set(Boolean.TRUE);
            removeById(id);
        });

        if (isD.get()){
            return Result.ok(ResultConstant.DELETE_SUCCESS, isD.get());
        }
        return Result.error(ResultConstant.D_EXIST_DELETE_FAIL, isD.get());
    }


    /**
     * 更新任务
     */
    @SneakyThrows
    public void updateSchedulerJob(Job job, String jobGroup) {
        Integer jobId = job.getId();
        // 判断是否存在
        JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
        if (scheduler.checkExists(jobKey)) {
            // 防止创建时存在数据问题 先移除，然后在执行创建操作
            scheduler.deleteJob(jobKey);
        }
        ScheduleUtils.createScheduleJob(scheduler, job);
    }


    /**
     * 校验cron表达式的合法性
     */
    private void checkCronIsValid(JobRO jobRO) {

        Opp.of(jobRO.getCronExpression()).orElseThrow(()->new NullPointerException("cron表达式不能为null"));

        boolean valid = CronUtils.isValid(jobRO.getCronExpression());

        Assert.isTrue(valid, "Cron表达式无效!");
    }
}
